Life of a Pixel 2018

This talk is about how Chrome turns web content into pixels. The entire process is called “rendering”.
We’ll describe what we mean by content, and what we mean by pixels, and then we’ll explain the magic in between.

这个演讲主要介绍chrome是如何把web content转成像素点的,整个过程被称为“rendering”。
我们首先描述什么叫做content,然后再介绍pixels,然后解释其中的魔法。

Chrome’s architecture is constantly evolving. This talk connects high-level concepts (which change slowly) to specific classes (which change frequently).
The details are based primarily on what is currently shipping in the canary channel (M69), but a few of the biggest future refactorings are mentioned in passing.

chrome的架构在不断发展。这个演讲将高级概念(变化缓慢)与特定类(经常变化)联系起来。细节基于canary(M69),一些比较大的未来的重构也会被提到。

“Content” is the generic term in Chromium for all of the code inside a webpage or the frontend of a web application.
It’s made of text, markup (surrounding the text), styles (defining how markup is rendered), and script (which can modify all of the above dynamically).
There are other kinds of content, which we won’t cover here.

“Content”是最通用的Chromium的术语,指的是网页内部或者是网络应用的前端代码。它由text,markup(环绕于文本),style(决定标记如何渲染),以及script(能够动态地改变上述提到的内容)。同时也有其他的content,但是我们在这里暂时不做介绍。

A real webpage is just thousands of lines of HTML, CSS, and JavaScript delivered in plain text over the network.
There’s no notion of compilation or packaging as you might find on other kinds of software platforms - the webpage’s source code is the input to the renderer.

一个真实的网页由成千行的纯文本的HTML,CSS以及JavaScript通过网络提供。你可能在其他软件平台找不到compilation或者packaging的概念,网页的源代码是renderer的输入。

Architecturally, the “content” namespace in the Chromium C++ codebase is responsible for everything in the red box.
Contrast with tab strip, address bar, navigation buttons, menus, etc. which live outside of “content”.
Key to Chrome’s security model: rendering happens in a sandboxed process.

从架构上说,”content“在Chromium C++ 代码的命名空间负责红框中的所有内容。
对比而言,标签条,地址栏,导航按钮,菜单,在”content“之外。
Chrome安全模型的关键是,在沙盒进程中进行渲染。

At the other end of the pipeline we have to get pixels onto the screen using the graphics libraries provided by the underlying operating system.
On most platforms today, that’s a standardized API called “OpenGL”. On Windows there’s an extra translation into DirectX. In the future, we may support newer APIs such as Vulkan.
These libraries provide low-level graphics primitives like “textures” and “shaders”, and let you do things like “draw a polygon at these coordinates into a buffer of virtual pixels”. But obviously they don’t understand anything about the web or HTML or CSS.

在管道的另一边,我们要利用操作系统下提供的图形库把像素点放置在屏幕上。现今,在大多数的平台上,有一个标准API叫做”OpenGL”。在Windows上,有额外到DirectX的转换。在将来,我们会支持新的API(类似Vulkan)。
这些库提供了低级图形基元(primitive),类似“纹理(texture)”以及“着色器(shader)”,让你能够做类似“在这些坐标处绘制一个多边形到虚拟像素缓冲区”这样的操作。但是显然他们并不理解任何和HTML和CSS相关的事情。

So the goal of rendering can be stated as: turn HTML / CSS / JavaScript into the right OpenGL calls to display the pixels.
But keep in mind a second goal as we describe the pipeline: We also want the right intermediate data structures to update the rendering efficiently after it’s produced, and answer queries about it from script or other parts of the system.

所以渲染的目标可以描述为:将HTML/CSS/JavaScript转化为正确的OpenGL的调用来展示像素。
但是要记住,我们描述这个管道的第二个目标是:我们同样想知道正确的中间数据生成后有效地更新,并且能在脚本或者系统的其他部分回答有关它的查询。

We break the pipeline into multiple “lifecycle stages”, generating those intermediate outputs.
We’ll first describe each stage of a working pipeline, then come back to the notion of efficient updating and introduce some concepts for optimization.

我们将管道分成多个“生命周期阶段”,产生中间输出。我们首先描述工作流水线的每个阶段,然后回到有效更新的概念,再介绍优化相关的概念。

HTML tags impose a semantically meaningful hierarchical structure on the document. For example, a

may contain two paragraphs, each with text. So the first step is to parse those tags to build an object model that reflects this structure.

HTML标签在文档上强加了语义上有意义的层次结构。 例如,

可能包含两个段落,每个段落都带有文本。 因此,第一步是解析这些标签以构建反映此结构的对象模型。

If you’ve taken computer science classes you may recognize this as a “tree”.

如果你上过计算机科学的课程,那么你就能认出来它是一棵树。

The DOM serves double duty as both the internal representation of the page, and the API exposed to script for querying or modifying the rendering.
The JavaScript engine (V8) exposes DOM web APIs as thin wrappers around the real DOM tree through a system called “bindings”.

DOM具有双重功能,它既能作为页面的内部展示,也能作为查询和更改呈现的脚本的公开API。Javascript引擎(V8)通过一个系统叫做“bindings”把真实的DOM tree封装成一个轻量的wrapper。

Having built the DOM tree, the next step is to process the CSS styles.
A CSS selector selects a subset of DOM elements that its property declarations should apply to.

构建了DOM tree以后,下一步是处理CSS styles。一个CSS选择器选择它属性声明了应当应用的DOM元素的子集。

Style properties are the knobs by which web authors can influence the rendering of DOM elements.
There are hundreds of style properties.

作者可以通过Style属性影响DOM元素的渲染。style属性有几百个。

Furthermore, it’s not trivial to determine which elements a style rule selects.
Some elements may be selected by more than one rule, with conflicting declarations for a particular style property.

此外,确定样式规则选择哪些元素并非易事。
某些元素可能由多个规则选择,并且具有特定样式属性的冲突声明。

The layout stage runs after the style recalc stage.
First, the layout tree is constructed. Then, we walk the layout tree, filling in the geometry data, and processing side effects of the geometry.

layout阶段在style重计算阶段以后进行。首先,layout tree构建,然后我们遍历这个layout tree,填充几何数据,然后处理几何产生的副作用。

Today, layout objects contain both inputs and outputs of the layout stage, without a clean separation between them.
For example, the LayoutObject acquires ownership of its element’s ComputedStyle object.
A new layout system called LayoutNG is expected to simplify the architecture, and make it easier to build new layout algorithms.

如今,layout对象包括layout阶段的输入和输出,它们之间的区分没有那么清晰。
比如,LayoutObject获取它元素的 ComputedStyle 对象的所有权。一个新的layout系统叫做LayoutNG,被期望用来简化架构,构建新的更简单的layout算法。

Now that we understand the geometry of our layout objects, it’s time to paint them.
Paint records paint operations into a list of display items.
A paint operation might be something like “draw a rectangle at these coordinates, in this color”.
There may be multiple display items for each layout object, corresponding to different parts of its visual appearance, like the background, foreground, outline, etc.
This is just a recording that can be played back later. We’ll see why that’s useful in a bit.

现在我们理解了layout object的几何形状,接下来就是绘制它们。
paint将paint操作记录为一列display item。一个paint操作类似于“在这些坐标用这个颜色绘制一个矩形”。对于每个layout object也许有多个display item,对应视觉外观的不同部分,比如背景,前景,轮廓等。~~~

It’s important to paint elements in the right order, so that they stack correctly when they overlap.
The order can be controlled by style.

以正确的顺序paint element非常重要,只有这样才能在元素覆盖的时候正常显示。这个顺序能被style所控制。

It’s even possible for an element to be partly in front of and partly behind another element.
That’s because paint runs in multiple phases, and each paint phase does its own traversal of a subtree.

一个元素部分在另外一个元素的前面或者后面是可能的。因为paint在不同阶段运行,每次的paint阶段做了自己子树的遍历。

The paint operations in the display item list are executed by a process called rasterization.
Each cell in the resulting bitmap holds values for four color channels.

在display item列表上进行paint操作被称为rasterization(光栅化)。位图上的每个像素点存着四个颜色通道的值。

The rastered bitmap is stored in memory, typically GPU memory referenced by an OpenGL texture object.
The GPU can also run the commands that produce the bitmap (“accelerated rasterization”).
Note that these pixels are not yet on the screen!

光栅化的位图在内存中存储,通常是被OpenGL纹理对象引用的GPU内存。
GPU能够运行产生位图的命令(“加速光栅化”)
注意到此时像素并不在屏幕上。

Rasterization issues OpenGL calls through a library called Skia. Skia provides a layer of abstraction around the hardware, and understands more complex things like paths and Bezier curves.
Skia is open-source and maintained by Google. It ships in the Chrome binary but lives in a separate code repository. It’s also used by other products such as the Android OS.
Skia’s GPU-accelerated codepath builds its own buffer of drawing operations, which is flushed at the end of the raster task.

Rasterization通过一个Skia的库产生OpenGL调用,Skia围绕硬件提供了一层抽象,能理解类似路径和贝塞尔曲线等更复杂的事情。
Skia开源且由谷歌维护。它附带在chrome二进制文件中,但是属于一个单独的代码库,同时也被其他产品,比如安卓系统使用。
Skia的GPU加速代码路径构建了它独立的绘制操作缓冲区,它会在光栅化任务结束的时候刷新。

Recall that the renderer process is sandboxed, so it can’t make system calls directly.
GL calls issued by Skia are actually proxied into a different process using a “command buffer”.
The GPU process receives the command buffer and issues the “real” GL calls through a set of function pointers.
Besides escaping the renderer sandbox, isolating GL in the GPU process protects us from unstable or insecure graphics drivers.

回顾之前说的渲染进程在沙盒中,所以并不能直接进行系统调用。
Skia发起的GL调用使用“command buffer”被代理给了另一个不同的进程。
GPU进程接受command buffer然后通过函数指针发起“真正”的GL调用。
逃离沙盒进程,GPU进程中独立的GL将从不稳定和不安全的图形驱动中保护我们。

Those GL function pointers are initialized by dynamic lookup from the system’s shared OpenGL library - or the ANGLE library on Windows.
ANGLE is another library built by Google; its job is to translate OpenGL to DirectX, which is Microsoft’s API for accelerated graphics on Windows.
There are also OpenGL drivers for Windows, but historically they have not been very high quality.

这些GL函数指针通过系统的共享OpenGL库(或Windows上的ANGLE库)进行动态查找来初始化。
ANGLE是Google建立的另一个库; 它的工作是将OpenGL转换为DirectX,这是微软在Windows上加速图形的API。
Windows也有OpenGL驱动程序,但从历史上看,它们的质量并不高。

Moving raster to the GPU process will improve performance.
It’s also needed to support Vulkan.

把光栅化移动到GPU进程能够改善性能。它同时需要支持Vulkan

We have now gone all the way from content to pixels in memory.
But note that the rendering is not static.
Running the full pipeline is expensive

现在我们已经走完在内存中从content到pixels的全程了。但是渲染本身不是静态的,走完管道全程总是代价昂贵的。

Change is modelled as animation frames.
Each frame is a complete rendering of the state of the content at a particular point in time.








Certain style properties cause a layer to be created for a layout object.
If a layout object doesn’t have a layer, it paints into the layer of the nearest ancestor that has one.

特定的样式属性会为layout对象创建layer。如果一个layout对象本身并没有layer,那么它将被绘制到最近的祖先节点上。

Building the layer tree is a new lifecycle stage on the main thread. Today, this happens before paint, and each layer is painted separately.

构建layer tree是主线程上的新生命周期阶段。如今,这发生在paint之前,每层都是单独paint的。



After paint is finished, the commit updates a copy of the layer tree on the compositor thread, to match the state of the tree on the main thread.

paint结束后,commit将更新compositor线程上的layer tree的拷贝,以匹配主线程上tree的状态。

Recall: raster is the step after paint, which turns paint ops into bitmaps.
Layers can be large - rastering the whole layer is expensive, and unnecessary if only part of it is visible.
So the compositor thread divides the layer into tiles.
Tiles are the unit of raster work. Tiles are rastered with a pool of dedicated raster threads. Tiles are prioritized based on their distance from the viewport.
(Not shown: a layer actually has multiple tilings for different resolutions.)

回想一下:raster在paint之后,它将paint ops转换为位图。
layer可能很大 - 整个layer都光栅化的成本很高,如果只有部分可见则不必要全部光栅化。
因此,compositor线程将layer划分为tiles。
tiles是raster work的基本单位。 专门的raster线程池对tiles进行光栅化。 根据tiles与viewport的距离确定tiles的优先级。
(未显示:layer实际上有不同分辨率的多个tilings。)

Once all the tiles are rastered, the compositor thread generates “draw quads”. A quad is like a command to draw a tile in a particular location on the screen, taking into account all the transformations applied by the layer tree. Each quad references the tile’s rastered output in memory (remember, no pixels are on the screen yet).
The quads are wrapped up in a compositor frame object which gets submitted to the browser process.

一旦所有tiles都被光栅化,compositor线程就会生成“draw quads”。 quads类似一个命令,考虑了图层树应用的所有变换在屏幕上的特定位置绘制tile。 每个quads在内存中引用了tile的光栅输出(请记住,屏幕上还没有像素)。
quads被包装在一个compositor frame object中,该对象被提交给浏览器进程。

The compositor thread has two copies of the tree, so that it can raster tiles from a new commit while drawing the previous commit.

合成器线程有两个树的副本,因此它可以在绘制上一次commit时,对新的commit进行光栅化tiles。

The browser process runs a component called the display compositor, inside a service called “viz” (short for visuals).
The display compositor aggregates compositor frames submitted from all the renderer processes, along with frames from the browser UI outside of the WebContents. Then it issues the GL calls to draw the quad resources, which go to the GPU process just like the GL calls from the raster workers.
On most platforms the display compositor’s output is double-buffered, so the quads draw into a backbuffer, and a “swap” command makes it visible.
(On OS X we do something a little different with CoreAnimation.)
Finally our pixels are on the screen. :)

浏览器进程在名为“viz”(visual的简称)的service中运行一个名为display compositor的组件。
display compositor聚合从所有renderer进程提交的compositor frame,以及来自WebContents外部的browser UI的frame。 然后它发起GL调用来绘制quad资源,这些资源就像来自raster worker的GL调用一样进入GPU进程。
在大多数平台上,display compositor的输出是双缓冲的,因此quads绘制到backbuffer,“swap”命令使其可见。
(在OS X上,我们使用CoreAnimation做了一些不同的事情。)
最后我们的像素在屏幕上。:)


文章转载自:Life of a Pixel 2018

推荐文章